/*
  Time-stamp: <2009-11-12 10:43:06 xinhaoyuan>
  
  File : intr.c
  
  Component for lower level handling of interrupting, including system
  exception, irq interrupting and user interrupting.

  In intr.c, when a interrupting is comming, the major handler just
  dispatch each request into minor handler. In my plan, the minor
  handler should not be blocked to process the request, but to deliver
  it to handler process.

  The file also contain routines interacting with interrupt controller.

  For function and definition of function and global variable, see the
  header file "intr.h".
  
 */
#define _EKOS_KERNEL_C_
#include <kernel/kernel.h>

KERNEL_EXPORT ex_handler_t ex_handlers[32];
KERNEL_EXPORT struct kpipe_t *irq_pipes[16];
KERNEL_EXPORT irq_handler_t irq_handlers[16];
KERNEL_EXPORT struct semaphore_t irq_semaphores[16];

KERNEL_EXPORT void
kexception(struct trapframe_t tf)
{
     if (ex_handlers[tf.num] != NULL) {
		  /* cprintf("Incoming TRAP frame at %p\n", &tf); */
		  /* cprint_trapframe(&tf); */
		  ex_handlers[tf.num](&tf);
	 }
     else
     {
     	  kprintf(" got kernel expection %d, error num %04x\n", tf.num, tf.err);
     	  monitor(&tf);
     }
}

KERNEL_EXPORT void
register_ex_handler(uint32_t n, ex_handler_t h)
{
     ex_handlers[n] = h;
}

KERNEL_EXPORT void
send_eoi(uint8_t irq)
{
     if(irq >= 8)
		  outb(PIC2_COMMAND, PIC_EOI);
	 
     outb(PIC1_COMMAND, PIC_EOI);
}

/*  
 * Enable the specified IRQ line.  
 */  
KERNEL_EXPORT void
enable_irq_line(int numirq)
{
	 if (numirq < 8) {
		  /* irq on master PIC. */  
		  outb(PIC1_DATA, inb(PIC1_DATA) & ~(1 << numirq));
	 } else {
		  /* irq on slave PIC. */
		  outb(PIC2_DATA, inb(PIC2_DATA) & ~(1 << (numirq - 8)));
	 }
}
  
/* 
 * Disable the specified IRQ line. 
 */
KERNEL_EXPORT void
disable_irq_line(int numirq)  
{  
	 if (numirq < 8) {  
		  /* irq on master PIC. */  
		  outb(PIC1_DATA, inb(PIC1_DATA) | (1 << numirq));  
	 } else {  
		  /* irq on slave PIC. */  
		  outb(PIC2_DATA, inb(PIC2_DATA) | (1 << (numirq - 8)));  
	 }  
}  

KERNEL_EXPORT void
kirq(struct trapframe_t tf)
{
	 if (envs[cur_env].xinfo.level > 0)
     	  /* when in user exception level, do not response irq */
     	  return ;

     if (irq_handlers[tf.num] != NULL)
	 {
		  if (irq_handlers[tf.num](&tf) == 0)
			   send_eoi(tf.num);
	 }
     else
     {
#ifdef K_INTR_DEBUG
	 cprintf(" got irq request %d\n", tf.num);
#endif
	 send_eoi(tf.num);
     }	 
}

KERNEL_EXPORT void
register_irq_handler(uint32_t irq, irq_handler_t h)
{
     irq_handlers[irq] = h;
}

KERNEL_EXPORT void
register_irq_pipe(uint32_t irq, struct kpipe_t *pipe)
{
     irq_pipes[irq] = pipe;
}

/* The following code are copied from OSDev.org with modifications {{{ */
KERNEL_EXPORT void
timer_init(uint32_t freq)
{
     // The value we send to the PIT is the value to divide it's input clock
     // (1193180 Hz) by, to get our required frequency. Important to note is
     // that the divisor must be small enough to fit into 16-bits.
     uint32_t divisor = 1193180 / freq;

     // Send the command byte.
     outb(0x43, 0x36);

     // Divisor has to be sent byte-wise, so split here into upper/lower bytes.
     uint8_t l = (uint8_t)(divisor & 0xFF);
     uint8_t h = (uint8_t)((divisor>>8) & 0xFF);

     // Send the frequency divisor.
     outb(0x40, l);
     outb(0x40, h);
}
/* }}} */

KERNEL_EXPORT uint32_t
intr_disable_begin(void)
{
     uint32_t result = read_eflags();
     asm volatile("cli");
     
     return result;
}

KERNEL_EXPORT void
intr_disable_end(uint32_t eflags)
{
     write_eflags(eflags);
}

KERNEL_EXPORT uint32_t
intr_enable_begin(void)
{
     uint32_t result = read_eflags();
     asm volatile("sti");
     
     return result;
}

KERNEL_EXPORT void
intr_enable_end(uint32_t eflags)
{
     write_eflags(eflags);
}


KERNEL_EXPORT void
st_handler(uint32_t esp, struct trapframe_t tf)
{
	 if (tf.num == 0) {
		  /* irq0 */
		  send_eoi(0);
#if 0
		  if (irq_handlers[0] != NULL)
		  {
			   irq_handlers[0](&tf);
		  }
#endif
		  scheduler(&esp, SCHED_HINT_PERIOD);
	 }
	 else if (tf.num == INT_YIELD)
	 {
		  /* user yield request */
		  scheduler(&esp, SCHED_HINT_FORCE);
	 }
}

KERNEL_EXPORT void
after_switching_hook(struct trapframe_t tf)
{
	 refresh_envs_monitor();
}


/*
  System handler for page fault.

  The handler simply uses system routine to fix the COW(copy on write)
  page, or throws a error message in case when it's really a page
  error.
*/
KERNEL_EXPORT void
pfhandler(struct trapframe_t *tf)
{
     void *addr = (void *)rcr2();
     bool handled = false;

	 struct env_t *env = envs + cur_env;

#ifdef K_INTR_DEBUG
     kprintf("catch page fault 0x%0x, eid = %d, err = %03b {", addr, env->eid, tf->err);
#endif

	 /*
	   Throws the exception to user handler when the func ptr is not
	   null.
	 */
	 if (dyninfo.upfhandler != 0 && env->xinfo.level < XLEVEL_MAX)
	 {
		  int unlock = !mutex_lock(&dyninfo.uxstack_lock);
		  cprintf("[pfhandler %d] user ex start\n", cur_env);
		  
#ifdef K_INTR_DEBUG
		  kprintf("using user page fault handler -- %p, level = %d\n", vpfhandler, env->xinfo.level);
		  if (env->xinfo.level != 0)
			   kprintf("  usp = %p\n", env->xinfo.usp);
		  breakpoint();
#endif

		  char *stack;
		  if (env->xinfo.level ++ == 0)
		  {
			   /* When first get into user handler, we need to
				* initialize the user stack ptr*/
			   env->xinfo.usp = 0;
			   stack = (char *)UXSTACK_TOP;
		  }
		  else if (tf->err & FEC_U)
		  {
			   /* When it come form the user mode, in this case, the
				* user stack ptr is already in user ex-stack, then we
				* need to adjust the ptr in order to preserve return
				* information in the stack.  */
			   stack = (void *)tf->stk.esp;
			   stack -= sizeof(uint32_t);

			   *(uint32_t *)stack = env->xinfo.usp;
			   env->xinfo.usp = (uint32_t)stack;
		  }
		  else stack = (char *)env->xinfo.usp;

		  /* pass arguments to user exception handler */
		  /* level */
		  stack -= sizeof(uint32_t);
		  *(uint32_t *)stack = env->xinfo.level;
		  /* err */
		  stack -= sizeof(uint32_t);
		  *(uint32_t *)stack = tf->err;
		  /* addr */
		  stack -= sizeof(uint32_t);
		  *(uint32_t *)stack = (uint32_t)addr;
		  /* tf */
		  stack -= sizeof(struct trapframe_t);	  
		  memmove(stack, tf, sizeof(struct trapframe_t));

		  /* Now we jump into user handler */
		  if (jump_user_pfhandler(dyninfo.upfhandler, (uint32_t)stack) == 0)
			   handled = true;

		  /* And then return the last level */
		  if (-- env->xinfo.level > 0)
			   if (tf->err & FEC_U)
			   {
					tf->stk.esp = env->xinfo.usp + sizeof(uint32_t);
					env->xinfo.usp = *(uint32_t *)(env->xinfo.usp);
			   }

		  cprintf("[pfhandler %d] user ex end\n", cur_env);
		  if (unlock) mutex_unlock(&dyninfo.uxstack_lock);
	 }
	 /* else we use the standard system handler */
	 else if ((addr < (void *)UMAP_TOP || PAGEDIR_IDX(addr) == PAGEDIR_IDX(KVPT))
		 && (tf->err & FEC_WR)) 
     {
		  /* open the interrupt door for multitasking */
		  EFLAGS_T f = intr_enable_begin();

		  if (fix_addr(addr, NULL) == 0)
			   handled = true;

		  intr_enable_end(f);
     }

	 /* If the error is still not be fixed, then we throw error.  */
     if (!handled)
     {
		  kprintf(" Page fault in addres %08x, err num %03b\n", addr, tf->err);
		  monitor(tf);
     }	 
#ifdef K_INTR_DEBUG
	 else
	 {
		  kprintf("}\n");
	 }
#endif
}
